﻿using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Rechy.RechyVSTool
{
    class CmdRightMenu
    {
        private RechyVSToolPackage pkg = null;
        private static FormStatistics fstats = null;
        private bool isOpened = false;

        public CmdRightMenu(RechyVSToolPackage pkg)
        {
            this.pkg = pkg;
        }
        public void Run(object sender, EventArgs e)
        {
            if(null == pkg)
            {
                return;
            }
            //pkg.lstExcludeEndsWith = pkg.dte.GetValue("ExcludeEndsWith");
            pkg.count = 0;
            pkg.selectedMenu = (PkgCmdIDList)((MenuCommand)sender).CommandID.ID;

            var table = new RunningDocumentTable(pkg);
            pkg.lstAlreadyOpenFiles = (from info in table select info.Moniker).ToList<string>();

            var selectedItem = pkg.dte.SelectedItems.Item(1);
            pkg.WriteLog("\r\n====================================================================================");
            pkg.WriteLog("Start ：" + DateTime.Now.ToString());

            Stopwatch sp = new Stopwatch();
            sp.Start();
            try
            {
                if(pkg.selectedMenu == PkgCmdIDList.RechyVSToolMenuSln1)
                {

                    if(selectedItem.Project != null)
                    {
                        pkg.dte.ExecuteCommand("ProjectandSolutionContextMenus.Project.OpenFolderinFileExplorer");
                    }
                    else if (selectedItem.ProjectItem != null)
                    {
                        string fileName = selectedItem.ProjectItem.FileNames[0];
                        bool isOpened = pkg.lstAlreadyOpenFiles.Exists(file => file.Equals(fileName, StringComparison.OrdinalIgnoreCase));
                        Window window = pkg.dte.OpenFile("{7651A703-06E5-11D1-8EBD-00A0C90F26EA}", fileName);
                        window.Activate();
                        pkg.dte.ExecuteCommand("File.OpenContainingFolder");
                        if (!isOpened)
                        {
                            window.Close(vsSaveChanges.vsSaveChangesYes);
                        }
                    }
                    else
                    {

                    }
                    return;
                }

                if(pkg.selectedMenu == PkgCmdIDList.RechyVSToolMenuSln2008)
                {
                    if(fstats == null || fstats.IsDisposed)
                    {
                        fstats = new FormStatistics();
                    }
                    fstats.Init();
                    fstats.Show();
                }
                if(selectedItem.Project == null && selectedItem.ProjectItem == null)
                {
                    ProcessSolution();
                }
                else if(selectedItem.Project != null)
                {
                    ProcessProject(selectedItem.Project);
                }
                else if(selectedItem.ProjectItem != null)
                {
                    if (selectedItem.ProjectItem.ProjectItems != null)
                    {
                        if (selectedItem.ProjectItem.ProjectItems.Count > 0)
                        {
                            ProcessProjectItems(selectedItem.ProjectItem.ProjectItems);
                        }
                        else
                        {
                            ProcessProjectItem(selectedItem.ProjectItem);
                        }
                    }
                    else
                    {
                        DealCommand(selectedItem.ProjectItem.FileNames[0]);
                    }
                }
            }
            catch(COMException)
            {
            }
            sp.Stop();

            pkg.WriteLog(string.Format("Finish：{0}  Times：{1}s  Files：{2}", DateTime.Now.ToString(), sp.ElapsedMilliseconds / 1000, pkg.count - 2));
            if(pkg.count > 4)
            {
                pkg.dte.ExecuteCommand("View.Output");
            }
        }

        private void ProcessSolution()
        {
            if(pkg.dte.Solution != null)
            {
                var projects = (from prj in new ProjectIterator(pkg.dte.Solution)
                                where prj.Kind == GuidList.GuidProjectTypeStringCS //只处理C#项目
                                select prj);

                projects.ForEach(prj => ProcessProject(prj));
            }
        }

        private void ProcessProject(Project project)
        {
            if(project != null)
            {
                ProcessProjectItems(project.ProjectItems);
            }
        }

        private void ProcessProjectItems(ProjectItems projectItems)
        {
            if(projectItems != null)
            {
                new ProjectItemIterator(projectItems).ForEach(item => ProcessProjectItem(item));
            }
        }

        private void ProcessProjectItem(ProjectItem projectItem)
        {
            isOpened = false;
            if(projectItem != null)
            {
                //必须是代码文件，且符合ExcludeEndsWith规则
                if(IsNotNeedProcess(projectItem))
                {
                    return;
                }
                DealCommand(projectItem.FileNames[0]);
            }
        }

        private void DealCommand(String fileName)
        {

            Window window = null;
            if (pkg.selectedMenu == PkgCmdIDList.RechyVSToolMenuSln2008)
            {
                int count = getCharLineCount(fileName);
                fstats.AddData(fileName, count);
                return;
            }
            else
            {

                pkg.WriteLog("Doing ：" + fileName);
                isOpened = pkg.lstAlreadyOpenFiles.Exists(file => file.Equals(fileName, StringComparison.OrdinalIgnoreCase));
                window = pkg.dte.OpenFile(GuidList.vsViewKindCode, fileName);
                window.Activate();
                try
                {
                    switch (pkg.selectedMenu)
                    {
                        case PkgCmdIDList.RechyVSToolMenuSln2001:
                            {
                                string crlf;
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                getCRLF(objSel, out crlf);

                                objSel.SelectAll();
                                pkg.dte.ExecuteCommand("Edit.FormatDocumentAstyle");
                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2002:
                            {
                                //! 删除注释和多余空行
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"('[^'\r\n]*)//([^'\r\n]*')", "$1[:=1]$2", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"(""[^""\r\n]*)//([^""\r\n]*"")", "$1[:=1]$2", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"//(?:[^!?@#:%&=xpr\-\+\*\[\{\r\n]*|[^!?@#:%&=xpr\-\+\*\[\{\r\n]+[!?@#:%&=xpr\-\+\*\[\{]+[^\r\n]*)(\r?\n)", "$1", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                //! 非贪婪模式*?
                                objSel.ReplacePattern(@"/\*(?:\r?\n|[^\r\n])*?\*/", "", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"^(?:[\s\t]*(\r?\n)){2,}", "$1", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern("[:=1]", "//");
                                objSel.StartOfDocument();
                                pkg.dte.ExecuteCommand("Edit.FormatDocumentAstyle");
                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2003:
                            {
                                //! 删除空行
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"^[\s\t]*\r?\n", "", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.StartOfDocument();
                                pkg.dte.ExecuteCommand("Edit.FormatDocumentAstyle");
                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2004:
                            {
                                //! C++逗号整理
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"('[^'\r\n]*)//([^'\r\n]*')", "$1[:=1]$2", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"(""[^""\r\n]*)//([^""\r\n]*"")", "$1[:=1]$2", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"^(#[^\r\n]*)(\r?\n+[\s\t]*)+,", "$1$2[:=2],", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern(@"([\s\t]*[^#\r\n]*?)(//[^\r\n]*)?(\r?\n+[\s\t]*)+,", "$1,$2$3", (int)vsFindOptions.vsFindOptionsRegularExpression);
                                objSel.SelectAll();
                                objSel.ReplacePattern("[:=1]", "//");
                                objSel.SelectAll();
                                objSel.ReplacePattern("[:=2]", "");
                                objSel.StartOfDocument();
                                pkg.dte.ExecuteCommand("Edit.FormatDocumentAstyle");
                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2005:
                            {
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;

                                objSel.StartOfDocument();
                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2006:
                            {
                                //! C++头部防重引用环绕
                                string aFirstName = fileName
                                                    .Substring(fileName.LastIndexOf("\\") + 1, (fileName.LastIndexOf(".") - fileName.LastIndexOf("\\") - 1))
                                                    .ToUpper();
                                aFirstName = Regex.Replace(aFirstName, @"[^a-zA-Z0-9]+", "_");

                                string aLastName = fileName
                                                   .Substring(fileName.LastIndexOf(".") + 1, (fileName.Length - fileName.LastIndexOf(".") - 1))
                                                   .ToUpper();
                                string aParentDir = fileName.Substring(0, fileName.LastIndexOf("\\"));
                                string aParentDirName = aParentDir
                                                        .Substring(aParentDir.LastIndexOf("\\") + 1)
                                                        .ToUpper();
                                if (aLastName == "H" || aLastName == "HPP" || aLastName == "HXX")
                                {
                                    const string tag1 = "INCLUDE_";
                                    const string tag2 = "/*";
                                    const string tag3 = "*/";

                                    bool isHave = false;
                                    int cmt = 0;
                                    int nowLine = 1;
                                    int cmtLine = 1;
                                    string crlf;
                                    TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                    getCRLF(objSel, out crlf);

                                    try
                                    {
                                        objSel.GotoLine(nowLine, true);
                                        nowLine++;
                                    }
                                    catch(Exception)
                                    {
                                        objSel.StartOfDocument();
                                    }
                                    while (!objSel.IsEmpty && nowLine > 1)
                                    {
                                        string ltxt = objSel.Text.Trim();
                                        if (ltxt.StartsWith("#ifndef " + tag1, false, null))
                                        {
                                            isHave = true;
                                            break;
                                        }
                                        else if (cmt >= 0 && ltxt.StartsWith(tag2, false, null))
                                        {
                                            cmt++;
                                        }
                                        if (cmt >= 0 && ltxt.EndsWith(tag3, false, null))
                                        {
                                            cmt++;
                                        }
                                        if (cmt == 2)
                                        {
                                            cmtLine = nowLine;
                                            cmt = -1;
                                        }
                                        //超过两行的注释，不认为是头注释
                                        if (nowLine > 2 && cmt == 0)
                                        {
                                            cmt = -1;
                                        }
                                        try
                                        {
                                            objSel.GotoLine(nowLine++, true);
                                        }
                                        catch(Exception)
                                        {
                                            break;
                                        }
                                    }
                                    if (!isHave)
                                    {
                                        objSel.GotoLine(cmtLine);
                                        string sname = tag1 + aParentDirName + "_" + aFirstName + "_" + aLastName;
                                        objSel.Insert("#ifndef " + sname + crlf + "#define " + sname + crlf + crlf);
                                        objSel.EndOfDocument();
                                        objSel.Insert(crlf + crlf + "#endif //" + sname + crlf);
                                    }
                                    objSel.StartOfDocument();
                                }

                            }
                            break;
                        case PkgCmdIDList.RechyVSToolMenuSln2007:
                            {
                                //! 文件头版权定义
                                string aFileName = fileName
                                                   .Substring(fileName.LastIndexOf("\\") + 1);
                                string aParentDir = fileName.Substring(0, fileName.LastIndexOf("\\"));
                                string aParentDirName = aParentDir
                                                        .Substring(aParentDir.LastIndexOf("\\") + 1);

                                string theName = aParentDirName + "/" + aFileName;
                                int cmt = 0;
                                bool isHave = false;
                                int nowLine = 1;
                                string crlf;
                                TextSelection objSel = pkg.dte.ActiveDocument.Selection as TextSelection;
                                getCRLF(objSel, out crlf);

                                try
                                {
                                    objSel.GotoLine(nowLine, true);
                                    nowLine++;
                                }
                                catch (Exception)
                                {
                                    objSel.StartOfDocument();
                                }
                                while (!objSel.IsEmpty && nowLine > 1)
                                {
                                    string ltxt = objSel.Text.Trim();
                                    if (cmt >= 0 && ltxt.StartsWith("/*", false, null))
                                    {
                                        isHave = true;
                                        break;
                                    }
                                    //! 超过三行的未找到注释则跳出
                                    if (nowLine > 3)
                                    {
                                        break;
                                    }
                                    try
                                    {
                                        objSel.GotoLine(nowLine++, true);
                                    }
                                    catch (Exception)
                                    {
                                        break;
                                    }
                                }
                                if (!isHave)
                                {
                                    objSel.StartOfDocument();
                                    objSel.Insert(string.Format(
                                                      "/{0}" + crlf +
                                                      " * Author:\t\t__AUTHOR__" + crlf +
                                                      " * FileName:\t{1}" + crlf +
                                                      " * DateTime:\t{2}" + crlf +
                                                      " * About:\t\t__ABOUT__" + crlf +
                                                      " {0}/" + crlf + crlf,
                                                      new String('*', 80),
                                                      theName,
                                                      DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
                                }
                                objSel.StartOfDocument();
                            }
                            break;
                        default:
                            break;
                    }
                }
                catch (COMException)
                {
                }
            }

            if (isOpened)
            {
                pkg.dte.ActiveDocument.Save(fileName);
            }
            else if (window != null)
            {
                window.Close(vsSaveChanges.vsSaveChangesYes);
            }
        }

        private bool IsNotNeedProcess(ProjectItem projectItem)
        {
            if(projectItem.FileCodeModel == null)
            {
                return true;
            }

            var input = projectItem.FileNames[0];

            if(null != pkg.lstExcludeEndsWith)
            {
                foreach(var item in pkg.lstExcludeEndsWith)
                {
                    if(input.EndsWith(item, true, null))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        private void getCRLF(TextSelection objSel, out string crlf)
        {
            crlf = "\r\n";
            objSel.SelectAll();
            string txt = objSel.Text;

            Regex reg = new Regex(@"[^\r\n]+\n");
            if(reg.Matches(txt).Count > 0)
            {
                crlf = "\n";
            }
        }

        private int getCharLineCount(string filename)
        {
            var count = 0;
            var reg = new Regex(@"[0-9A-Za-z]+");
            var reader = new StreamReader(filename);
            try
            {
                while(!reader.EndOfStream)
                {
                    if(reg.Matches(reader.ReadLine()).Count > 0)
                    {
                        count++;
                    }
                }
            }
            finally
            {
                if(reader != null)
                    reader.Dispose();
            }

            return count;
        }

        private bool getAllContent(string filename, out string content)
        {
            try
            {
                StreamReader streamReader = new StreamReader(filename);
                content = streamReader.ReadToEnd();
                return true;
            }
            catch(Exception)
            {
            }
            content = null;
            return false;
        }

    }
}
